package com.wy.panda.common.sign;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;

import com.wy.panda.common.model.R;
import com.wy.panda.common.sign.constant.AuthConstants;
import com.wy.panda.common.sign.constant.SignMethodEnum;
import com.wy.panda.common.sign.constant.Validator;
import com.wy.panda.common.util.DateUtils;
import com.wy.panda.common.util.StringUtils;
import com.wy.panda.exception.PandaAccessDeniedException;

import lombok.extern.slf4j.Slf4j;

/**
 * 签名验证处理器抽象类
 *
 * @author WangYuan
 * @since 2020年11月5日 下午2:42:55
 */
@Slf4j
public abstract class AbstractSignValidationHandler implements AuthConstants, ISignValidationHandler {

	@Override
	public boolean handle(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//请求时间戳验证
		String timestamp = getTimestamp(request);
		if(!validateTimestamp(timestamp, response)) {
			return false;
		}
		
		//app的key和secret验证
		String appSecret = PandaSignConfig.appMap().get(getAppKey(request));
		if(!validateAppSecret(appSecret, response)) {
			return false;
		}
		
		//签名方法验证
		String signMethod = getSignMethod(request);
		IValidator validator = getValidator(signMethod);
		if(validator == null) {
			sendAuthError(response, new PandaAccessDeniedException(REQUEST_SIGN_METHOD_KEY + " is invalidate."));
			return false;
		}
		
		//签名参数验证
		String sign = getSign(request);
		if(StringUtils.isBlank(sign)) {
			sendAuthError(response, new PandaAccessDeniedException("Invalid signature"));
			return false;
		}
		
		//获取参与签名的参数
		Map<String, String> signParams = getSignParameter(request);
		if(signParams == null) {
			signParams = new HashMap<>();
		}
		
		//签名验证
		try {
			validator.validate(signParams, sign, appSecret);
		} catch (Exception e) {
			sendAuthError(response, e);
			return false;
		}
		
		return true;
	}
	
	protected abstract String getTimestamp(HttpServletRequest request);
	
	protected abstract String getAppKey(HttpServletRequest request);
	
	protected abstract String getSignMethod(HttpServletRequest request);
	
	protected abstract String getSign(HttpServletRequest request);
	
	protected abstract Map<String, String> getSignParameter(HttpServletRequest request);
	
	private boolean validateTimestamp(String timestamp, HttpServletResponse response) throws ServletException, IOException {
		if(StringUtils.isBlank(timestamp)) {
			sendAuthError(response, new PandaAccessDeniedException(REQUEST_TIMESTAMP_KEY + " is invalidate."));
			return false;
		}
		Date requestDate = null;
		try {
			requestDate = DateUtils.parseDate(timestamp, "yyyy-MM-dd HH:mm:ss");
		} catch (ParseException e) {
			log.error("解析请求时间戳错误，时间戳格式不正确，正确格式应为：yyyy-MM-dd HH:mm:ss；当前格式为：{}", timestamp);
			sendAuthError(response, new PandaAccessDeniedException(REQUEST_TIMESTAMP_KEY + " is invalidate, format is error."));
			return false;
		}
		long requestTime = requestDate.getTime();
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.MINUTE, -10);
		long validStartTime = calendar.getTimeInMillis();
		calendar.add(Calendar.MINUTE, 20);
		long validEndTime = calendar.getTimeInMillis();
		if(requestTime < validStartTime || requestTime > validEndTime) {
			sendAuthError(response, new PandaAccessDeniedException(REQUEST_TIMESTAMP_KEY + " is invalidate."));
			return false;
		}
		
		return true;
	}
	
	private boolean validateAppSecret(String appSecret, HttpServletResponse response) throws ServletException, IOException {
		if(StringUtils.isBlank(appSecret)) {
			sendAuthError(response, new PandaAccessDeniedException(REQUEST_APP_KEY + " is invalidate."));
			return false;
		}
		
		return true;
	}
	
	private IValidator getValidator(String code) {
		SignMethodEnum signMethod = SignMethodEnum.getByCode(code);
		if(signMethod == null) {
			return null;
		}
		
		IValidator validator = null;
		for(IValidator item : PandaSignConfig.validator()) {
			Validator annotation = item.getClass().getAnnotation(Validator.class);
			if(annotation == null) {
				continue;
			}
			
			if(signMethod.equals(annotation.signMethod())) {
				validator = item;
				break;
			}
		}
		
		return validator;
	}
	
	private void sendAuthError(HttpServletResponse response, Exception e) throws IOException {
		response.setContentType(MediaType.APPLICATION_JSON_VALUE);
		
		PrintWriter writer = response.getWriter();
		writer.write(R.error(e.getMessage()).toJSONString());
		response.flushBuffer();
	}
	
}
